概述
该分析主要是在内存dump出栈时,我们需要将其与源码对应起来。
本机分析准备
本机分析有一定的要求:
vmlinuxlinux-headerlinux-source
其中linux-header必须要有的,我们需要使用到里面的一些工具。
方法1
==条件:==本机如果在usr/lib/boot/debug/vmlinux-$(uname -r) 如果没有,则可使用教程安装Linux 内核的调试符号文件(symbol file)。
安装方法(ubuntu为例)
方法1
如果内核源码对应交的叉编译存在kernel/vmlinux,可以直接复制到:
sudo cp /path/to/your/vmlinux /usr/lib/boot/debug/方法2
在线方法安装调试符号表,方法在:安装符号表
sudo apt get linux-image-$(uname -r)-dbg方法2
==条件:==在/boot下有vmlinuz。 我们使用命令:
file /boot/vmlinuz-$(unmae -r)出现这样的打印:
vmlinuz-6.5.13: Linux kernel x86 boot executable bzImage, version 6.5.13 (root@Uubnut22) #1 SMP PREEMPT_DYNAMIC Sat Jul 13 19:07:28 CST 2024, RO-rootFS, swap_dev 0XB, Normal VGA还可以使用查看头文件信息:
objdump -h /boot/vmlinuz-$(uname -r)接着我们提取出vmlinux
# 从`vmlinux`中提取extract-vmlinux
# 或如下命令安装
sudo apt install binutils
# 如下命令生成:
/usr/src/linux-headers-6.5.0-41-generic/scripts$ ./extract-vmlinux /boot/vmlinuz-$(uname -r) > /tmp/vmlinux-$(uname -r)可以再tmp目录看到vmlinux。 接着我们需要验证vmlinux是否有调试符号表:
file /tmp/vmlinux-$(uname -r)若有如下信息中有with debug_info,则表示为可调试:
vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=f3db60bb57359cd5e4ea398a89c22f17a89a6ff5, with debug_info, not stripped
# 也可以使用如下命令
readelf -S vmlinux|grep debug
# 出现便可调试
[37] .debug_info PROGBITS 0000000000000000 04035670否则无法调试。
方法3
如果有
linux-source则可以使用源码重新生成vmlinux
上面是生成vmlinux的方法。
交叉分析准备
它主要是利用交叉编译下会生成
vmlinux。
具体分析过程
所有的调试都需要调试生成了
vmlinux我们这里使用本机调试,但是需要Linux-header,我么这里使用的是6.5.13,但使用:apt install linux-headers-6.5.13没有找到,只有6.5.0,可以通过手动下载,参考
接着我们准备一个问题module,callTraceTest.c:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
static int __init call_trace_init(void)
{
char *p = (char *)123456789;
printk("call trace init\n");
printk("*p = %d\n", *p);
return 0;
}
static void __exit call_trace_exit(void)
{
printk("call trace exit\n");
return;
}
module_init(call_trace_init);
module_exit(call_trace_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PengXuan");
MODULE_DESCRIPTION("call trace demo");
MODULE_VERSION("1.0");MACHINE=$(shell uname -m)
#ARCH=x86_64
ARCH=arm
ifeq "${ARCH}" "x86_64"
#ubuntu上编译自己的驱动
KERNELDIR=/usr/src/linux-headers-$(shell uname -r)
else
ifneq "${MACHINE}" "x86_64"
#在目标机器上直接编译
KERNELDIR=/usr/src/linux-headers-$(shell uname -r)
else
#交叉编译
KERNELDIR=/home/forlinx/work/lichee/linux-3.10
COMPILER_PATH=/home/forlinx/work/lichee/out/sun8iw11p1/linux/common/buildroot/host/usr/bin
CROSS_COMPILE=$(COMPILER_PATH)/arm-linux-gnueabihf-
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
MAKE=make
endif
endif
BUILD_DIR=$(shell pwd)/build
obj-m := callTraceTest.o
all: Makefile
ifeq "${ARCH}" "x86_64"
${MAKE} -C ${KERNELDIR} M=$(shell pwd) modules
else
ifeq "${MACHINE}" "x86_64"
#交叉编译
${MAKE} -C ${KERNELDIR} M=$(shell pwd) modules
else
#本机编译
${MAKE} -C ${KERNELDIR} M=$(shell pwd) modules
endif
endif
.PHONY:clean
clean:
rm -f *.ko *.mod *.mod.c *.mod.o *.o *.order *.symvers .*.cmdUbuntu上的分析
该系统分许需要使用到源码,编译源码方法可以按照编译源码,然后在按嵌入式系统方式进行分析。
嵌入式系统上的分析
这里使用的是:
RK3588 Fireflykernel 5.10.198- 源码放在
ubuntu系统上
将上面的驱动程序编译,然后:
sudo insmod callTraceTest.ko将串口出现的call trace或者通过dmesg获取的dump信息保存至一个call_trace.log的文件。
目标系统上分析
这要求目标系统(嵌入式)有
addr2line工具,同时要求目标设备上有vmlinux文件。 一般在/boot,/usr/lib/debug/boot/搜索
这种方法要求编出很多工具,对于一些简陋的嵌入式系统并不友好,所以不说明其分析方法(实际和下面在宿主机分析原理类似)。
宿主机上分析
我们编译的内核源码在ubuntu中,且它的外部模块为callTraceTest.ko。我们先确定几个参数:
vmlinux的位置,假设为:RK3588_SOURCE/kernel/vmlinux- 交叉编译工具的位置,假设为:
RK3588_SOURCE/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin decode_stacktrace.sh的位置,假设为:RK3588_SOURCE/kernel/scriptsaddr2line的位置(一般与交叉编译工具同目录)- 外部模块
callTraceTest.ko,假设为:call_trace接下来,我们按照下面的步骤进行分析:
- 先进入
call_trace - 然后执行如下命令:
export CROSS_COMPILE="/home/px/rk3588/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-"
RK3588_SOURCE/kernel/scripts/decode_stacktrace.sh RK3588_SOURCE/kernel/vmlinux ./ ./ < call_trace.log
# 使用方法(2种,这里使用的是第2种):
1. decode_stacktrace.sh -r 5.10.160 [base_path] [module_path] < call_trace.log
2. decode_stacktrace.sh [vmlinux_path] [base_path] [module_path] < call_trace.log- 等待分析完成。
可以看到,出错的地址被解析出来了,这里是因为我在目标机器编译后,再copy到宿主机器上的,所以可以看到基地址与内核函数的基地址稍微不一样,我们只需要按照框中提示去翻阅源码即可。
可以看到,出错的地址被解析出来了,这里是因为我在目标机器编译后,再